
package todolist.entity;

import java.util.Date;
import todolist.personalization.DateTimeDescriptionCustomizer;
import todolist.util.DateUtils;

/**
 * Abstraction of date and/or time Description in the real world, to minute precision.
 * Could be for Start or End Date/Time.
 *
 * This class is meant to be initialised upon instantiation.
 * This class is immutable, a new DateTimeEntity should be created if another date/time description is to be made.
 * The Internal Date will not be null, and will exclude Time portion as described.
 * Descriptor, if none, will be an empty String.
 * 
 * <table>
 * <th><td colspan=5>Description truth table:</td></th>
 * <tr><td></td><td>Time</td>	<td>Strict</td>	<td>Type</td>							<td>Example</td></tr>
 * <tr><td></td><td>F</td>		<td>F</td>		<td>AROUND that day</td>				<td>"around 01/01/2012"</td></tr>
 * <tr><td></td><td>F</td>		<td>T</td>		<td>On/before/after that day</td>		<td>"on 01/01/2012"		<-- second most precise</td></tr>
 * <tr><td></td><td>T</td>		<td>F</td>		<td>AROUND that day+time</td>			<td>"around 01/01/2012 22:30"</td></tr>
 * <tr><td></td><td>T</td>		<td>T</td>		<td>On/before/after that day+time</td>	<td>"on 01/01/2012 22:30"	<-- most precise</td></tr>
 * </table>
 *
 * @author Ivan
 */
public class DateTimeEntity implements Comparable<DateTimeEntity> {

	private static final int THIS_OBJECT_LESS = -1;
	private static final int THIS_OBJECT_GREATER = 1;
	
	// all 4 fields readonly
	private Date _dtmInternalDate;
	private boolean _blnHasTimeComponent;
	private boolean _blnIsStrict;
	private String _strDescriptor; // never null
	
	/**
	 * Initialization is completely done here.
	 * null descriptor is converted to empty String.
	 * @throws IllegalArgumentException
	 */
	public DateTimeEntity(Date pdtmInternalDate, boolean pblnTime, boolean pblnStrict, String pstrDescriptor) throws IllegalArgumentException {
		// validation and conversion of input
		if (pdtmInternalDate == null)
			throw new IllegalArgumentException("DateTimeEntity.DateTimeEntity(): Date required.");
		if (!pblnTime)
			pdtmInternalDate = DateUtils.removeTimeFromDate(pdtmInternalDate);
		if (pstrDescriptor == null)
			pstrDescriptor = "";
		if (!pstrDescriptor.equals("")) { // attempt to set descriptor
			pstrDescriptor = DateTimeDescriptionCustomizer.getInstance().findDTInputDescriptor(pstrDescriptor);
			if (pstrDescriptor == null) // no such descriptor found
				throw new IllegalArgumentException("DateTimeEntity.DateTimeEntity(): Descriptor is invalid.");
		}
		// initialise fields
		_dtmInternalDate = DateUtils.removeSecondsFromDate(pdtmInternalDate); // copied, prevents outside access to the internal date
		_blnHasTimeComponent = pblnTime;
		_blnIsStrict = pblnStrict;
		_strDescriptor = pstrDescriptor;
	}
	
	// getters

	/**
	 * Date/Time of the Description. Should not be null.
	 */
	public Date getInternalDate() {
		return new Date(_dtmInternalDate.getTime()); // clone to prevent outside access to the internal date
	}

	/**
	 * true only if this Description records both Date and Time.
	 * Expect Time portion to be normalised if false.
	 */
	public boolean hasTimeComponent() {
		return _blnHasTimeComponent;
	}

	/**
	 * false when this Description implies "around" a Date/Time.
	 */
	public boolean isStrict() {
		return _blnIsStrict;
	}

	/**
	 * Description prefix of the Date/Time. Never null.
	 */
	public String getDescriptor() {
		return _strDescriptor;
	}
	
	// overrides
	
	/**
	 * true only if exact description is the same.
	 */
	@Override
	public boolean equals(Object pobjOther) {
		if (pobjOther == null || !(pobjOther instanceof DateTimeEntity))
			return false;
		DateTimeEntity entOther = (DateTimeEntity) pobjOther;
		return
			_dtmInternalDate.equals(entOther.getInternalDate()) &&
			_blnHasTimeComponent == entOther.hasTimeComponent() &&
			_blnIsStrict == entOther.isStrict() &&
			_strDescriptor.equals(entOther.getDescriptor());
	}

	/**
	 * Returns -ve if this instance is smaller than (generally before) the other, 0 if completely equal, +ve if greater (generally after), according to rules below.
	 * (1) If other instance is null, return this instance smaller.
	 * (2) If Dates are non-equal, return comparison of internal Dates.
	 * (3) If only one Date has Time component, the one having time component is smaller.
	 * (4) If only one Date is Strict, the one that is Strict is smaller.
	 * (5) Return natural String comparison based on Date Descriptor. Empty String will be least.
	 */
	@Override
	public int compareTo(DateTimeEntity pentOther) {
		if (pentOther == null) // if (1)
			return THIS_OBJECT_LESS;
		if (!_dtmInternalDate.equals(pentOther.getInternalDate())) // if (2)
			return _dtmInternalDate.compareTo(pentOther.getInternalDate());
		if (_blnHasTimeComponent && !pentOther.hasTimeComponent()) // if (3)
			return THIS_OBJECT_LESS;
		if (!_blnHasTimeComponent && pentOther.hasTimeComponent()) // if (3)
			return THIS_OBJECT_GREATER;
		if (_blnIsStrict && !pentOther.isStrict()) // if (4)
			return THIS_OBJECT_LESS;
		if (!_blnIsStrict && pentOther.isStrict()) // if (4)
			return THIS_OBJECT_GREATER;
		return _strDescriptor.compareTo(pentOther.getDescriptor()); // (5)
	}

	/**
	 * Converts this DT description to String. Never null.
	 */
	@Override
	public String toString() {
		String strReturnValue = "";
		if (!_blnIsStrict) // around
			strReturnValue += DateTimeDescriptionCustomizer.getInstance().getDTDisplayNonStrict() + " ";
		if (!_strDescriptor.equals("")) // week of
			strReturnValue += _strDescriptor + " ";
		if (_blnHasTimeComponent) // + 00:00
			strReturnValue += DateUtils.formatDateTime(_dtmInternalDate);
		else
			strReturnValue += DateUtils.formatDateOnly(_dtmInternalDate);
		return strReturnValue;
	}
	
}
